/**
 * Copyright 2016-2018 mobaas.com
 */
package com.mobaas.devops.controller;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mobaas.devops.Config;
import com.mobaas.devops.model.AccessLog;
import com.mobaas.devops.model.Container;
import com.mobaas.devops.model.ContainerStat;
import com.mobaas.devops.model.ErrorLog;
import com.mobaas.devops.model.NginxLog;
import com.mobaas.devops.service.AccessLogService;
import com.mobaas.devops.service.BindingService;
import com.mobaas.devops.service.ErrorLogService;
import com.mobaas.devops.service.ContainerService;

/**
 * 
 * @author billy zhang
 * 
 */
@RestController
@RequestMapping("/api")
public class ApiController {

	private static SimpleDateFormat NGINX_DATE_FORMAT = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);
	private static byte[] lockObj = new byte[0];

	@Autowired
	private ErrorLogService errlogService;
	
	@Autowired
	private AccessLogService acclogService;
	
	@Autowired
	private ContainerService containerService;
	
	@Autowired
	private BindingService bindingService;
	
	@Autowired
	private Config config;
	
	@Autowired
	private ObjectMapper jsonMapper;
	
	private ExecutorService threadPool;
	
	private ExecutorService getThreadPool() {
		if (threadPool == null) {
			synchronized (lockObj) {
				if (threadPool == null) {
					threadPool = Executors.newFixedThreadPool(config.getThreadPoolSize());
				}
			}
		}
		
		return threadPool;
	}
	
	@RequestMapping("saveerrorlog")
	public String saveErrorLog(String log) throws IOException {
		
		ErrorLog errlog = jsonMapper.readValue(log, ErrorLog.class);
		//errlog.setLogTime( new Date(plog.getTimestamp()) );
		
		errlogService.insertErrorLog(errlog);
		
		return "success";
	}
	
	@RequestMapping("saveaccesslog")
	@ResponseBody
	public String saveAccessLog(@RequestBody NginxLog nlog) {
		
		getThreadPool().execute(new SaveAccessLogTask(nlog));
			
		return "success";
	}
	
	/**
	 * 用于单元测试
	 */
	@RequestMapping("saveaccesslog2")
	@ResponseBody
	public String saveAccessLog2(@RequestBody NginxLog nlog) {
		
		try {
			internalSaveAccessLog(nlog);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return "fail";
		}
			
		return "success";
	}
	
	@RequestMapping("savecontainerstat")
	@ResponseBody
	public String saveContainerStat(@RequestBody List<ContainerStat> statlist) {
		
		getThreadPool().execute(new SaveContainerStatTask(statlist));
			
		return "success";
	}
	

	@RequestMapping("savecontainerstats2")
	@ResponseBody
	public String saveContainerStats2(@RequestBody List<ContainerStat> statlist) {
		
		try {
			internalSaveContainerStat(statlist);
		} catch (Exception ex) {
			ex.printStackTrace();
			return "fail";
		}
		return "success";
	}
	
	
	private void internalSaveAccessLog(NginxLog nlog) throws UnsupportedEncodingException {
	
		if (nlog == null) 
			return;

		AccessLog reqLog = new AccessLog();
		
		try {
			String[] strs = nlog.getTimeLocal().split(" ");
			reqLog.setTime(NGINX_DATE_FORMAT.parse(strs[0])); //"timeLocal":"04\/Mar\/2018:22:41:35 +0800",
		} catch (ParseException e) {
			e.printStackTrace();
			reqLog.setTime(new Date());
		} 
		reqLog.setHost(nlog.getHost());//"host":"www.yuntik.com",
		String uri =  URLDecoder.decode( nlog.getUri(), "utf-8" );//"uri":"\/lib\/102589_1.aspx",
		int n = uri.indexOf("?");
		if (n > 0) {
			reqLog.setPath(uri.substring(0, n));
			reqLog.setQuery(uri.substring(n+1));
		} else {
			reqLog.setPath(uri);
		}
		reqLog.setRemoteAddr(nlog.getRemoteAddr()); //"remoteAddr":"100.116.208.174",
		reqLog.setStatus(Integer.parseInt(nlog.getStatus()));//"status":"302",
		reqLog.setBodyBytesSent(Integer.parseInt(nlog.getBodyBytesSent()));//"bodyBytesSent":"164",
		
		reqLog.setUpstreamHost(nlog.getUpstreamAddr()); //"upstreamAddr":"10.31.72.150:32768",
		
		int upStatus = !"-".equalsIgnoreCase(nlog.getUpstreamStatus()) ? 
					Integer.parseInt(nlog.getUpstreamStatus()) : -1;
					
		reqLog.setUpstreamStatus(upStatus);//"upstreamStatus":"302",
			
		float upRespTime = !"-".equalsIgnoreCase(nlog.getUpstreamResponseTime()) ?
				Float.parseFloat(nlog.getUpstreamResponseTime()) : -1;
		reqLog.setUpstreamResponseTime(Math.round(upRespTime * 1000));//"upstreamResponseTime":"0.009"
		
		Container cont = containerService.selectContainerByHost(reqLog.getUpstreamHost());
		if (cont != null) {
			reqLog.setAppId(cont.getAppId());
		}
		acclogService.insertAccessLog(reqLog);
			
	}
	
	private void internalSaveContainerStat(List<ContainerStat> statlist) {
		
		if (statlist == null || statlist.size() == 0) 
			return;

		Map<String, Integer> contMap = containerService.selectLastContainerStatMap();
		int nextSeqNo = 1;
		if (contMap.size() > 0) {
			nextSeqNo = contMap.values().iterator().next() + 1;
		}
		
		for (ContainerStat stat : statlist) {
			stat.setSeqNo(nextSeqNo);
			containerService.insertContainerStat(stat);
			
			Container cont = containerService.selectContainerById(stat.getContainerId());
			if (cont != null) {
				cont.setActiveTime(new Date());
				containerService.updateContainer(cont);
				
				contMap.remove(stat.getContainerId());
			} else {
				cont = new Container();
				cont.setContainerId(stat.getContainerId());
				cont.setAppId(cont.getAppId());
				cont.setHostPort(stat.getPublicPort());
				cont.setHostIp(stat.getHostIp());
				cont.setAddTime(new Date());
				cont.setActiveTime(new Date());
				containerService.insertContainer(cont);
				
				bindingService.register(cont);
			}
		}
		
		if (contMap.size() > 0) {
			for (String containerId : contMap.keySet()) {
				bindingService.unregister(containerId);
			}
		}
	}
	
	private class SaveAccessLogTask implements Runnable {

		private NginxLog nlog;
		
		public SaveAccessLogTask(NginxLog nlog) {
			this.nlog = nlog;
		}
		
		@Override
		public void run() {

			try {
				internalSaveAccessLog(nlog);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	private class SaveContainerStatTask implements Runnable {

		private List<ContainerStat> statlist;
		
		public SaveContainerStatTask(List<ContainerStat> statlist) {
			this.statlist = statlist;
		}
		
		@Override
		public void run() {
			try {
				internalSaveContainerStat(statlist);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
}
